package com.wry.wallet.aspect;

import com.wry.wallet.annotation.Flow;
import com.wry.wallet.entity.AccountFlow;
import com.wry.wallet.enums.DealStatusEnum;
import com.wry.wallet.enums.DealTypeEnum;
import com.wry.wallet.mapper.AccountFlowMapper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * <h1>  <h1>
 *
 * @author wry
 * @since 2023/3/31 13:30
 */
@Component
@Aspect
@Slf4j
public class FlowAspect {

    @Resource
    private AccountFlowMapper accountFlowMapper;
    @Pointcut("@annotation(com.wry.wallet.annotation.Flow)")
    public void saveFlowAspect(){}

    /**
     * 后置通知：正常返回后执行
     */
    @AfterReturning(value = "saveFlowAspect()")
    public void afterReturnMethod(JoinPoint joinPoint) throws Exception {
        AccountFlow flow = saveAccountFlow(joinPoint);
        flow.setDealStatus(DealStatusEnum.SUCCESS);
        flow.setDealDesc("交易成功");
        accountFlowMapper.insert(flow);
    }

    /**
     * 异常通知：方法抛出异常后执行
     */
    @AfterThrowing(value = "saveFlowAspect()", throwing = "e")
    public void throwMethod(JoinPoint joinPoint, RuntimeException e) throws Exception {
        AccountFlow flow = saveAccountFlow(joinPoint);
        flow.setDealStatus(DealStatusEnum.FAIL);
        flow.setDealDesc(e.getMessage());
        accountFlowMapper.insert(flow);
    }

    /**
     * 保存账户流水信息
     */
    private AccountFlow saveAccountFlow(JoinPoint joinPoint) throws Exception {
        AccountFlow flow = new AccountFlow();
        flow.setDealCreatedAt(LocalDateTime.now());
        flow.setDealFlowNumber(UUID.randomUUID().toString());
        flow.setDealType(getDealType(joinPoint));
        Map<String, Object> param = getAccountNumber(joinPoint);
        flow.setDealAmount((BigDecimal) param.get("amount"));
        flow.setDealAccountNumber((String) param.get("accountNumber"));
        return flow;
    }

    /**
     * 获取注解type具体值
     */
    private DealTypeEnum getDealType(JoinPoint joinPoint) throws Exception{
        // 获取类名
        final String className = joinPoint.getTarget().getClass().getName();
        // 获取方法名
        final String methodName = joinPoint.getSignature().getName();
        // 获取参数
        final Object[] arguments = joinPoint.getArgs();
        // 反射获取实例对象
        final Class<?> LogClass = Class.forName(className);
        // 通过实例对象获取所有方法
        final Method[] methods = LogClass.getMethods();
        DealTypeEnum type = null;
        for (Method method : methods) {
            // 判断方法名是否一样
            if (method.getName().equals(methodName)){
                final Class<?>[] classes = method.getParameterTypes();
                // 对比参数数组的长度
                if (classes.length == arguments.length){
                    //获取注解里的type信息
                    type = method.getAnnotation(Flow.class).type();
                    break;
                }
            }
        }
        return type;
    }

    /**
     * 获取当前方法参数
     */
    private Map<String, Object> getAccountNumber(JoinPoint joinPoint){
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        // 请求的方法参数值
        Object[] args = joinPoint.getArgs();
        // 请求的方法参数名称
        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
        String[] paramNames = u.getParameterNames(method);
        Map<String, Object> param = new HashMap<>();
        if (args != null && paramNames != null) {
            for (int i = 0; i < args.length; i++) {
                param.put(paramNames[i],args[i]);
            }
        }
        return param;
    }
}
